﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpMUD.Framework.Services.Net;
using SharpMUD.Framework.Net.Telnet;
using SharpMUD.Framework.Services.Configuration;
using SharpMUD.Framework.ComponentModel;
using SharpMUD.Framework.Net;
using System.Net;

namespace SharpMUD.Framework.Services.Telnet
{
    [ServiceDependency(typeof(TelnetServer))]
    public abstract class TelnetFilterServiceBase
        :   INetworkConnectionFilterService<TelnetSession>
    {
        internal TelnetFilterServiceBase() { }

        #region INetworkConnectionFilterService<TelnetSession> Members

        public abstract bool? ApproveConnectionAttempt(TelnetSession connection);

        #endregion

        #region IService Members

        public IServicesHost ServicesHost
        {
            get;
            private set;
        }

        public void Initialize(IServicesHost servicesHost)
        {
            this.ServicesHost = servicesHost;

            this.OnInitialize();

            AttachToTelnetConnectionListeners();

            this.IsInitialized = true;
        }

        public bool IsInitialized
        {
            get;
            private set;
        }

        #endregion

        private void AttachToTelnetConnectionListeners()
        {
            var telnetConnectionListeners = this.ServicesHost.Services.Where(s => s is TelnetServer).Select(s => s as TelnetServer)
                .SelectMany(ts => ts.ConnectionListeners).ToList();

            telnetConnectionListeners.ForEach(AttachToTelnetConnectionListener);
        }

        protected abstract void OnInitialize();

        protected abstract void AttachToTelnetConnectionListener(INetworkConnectionListener cl);
    }

    public class TelnetConnectionAttemptSpamFilter
        : TelnetFilterServiceBase
    {
        public TelnetConnectionAttemptSpamFilter() { }

        protected override void OnInitialize()
        {
            _BanThresholds.Add(
                new AttemptThreshold()
                {
                    AttemptTimeSpan = new TimeSpan(0,0,30),
                    Count = 5,
                    BanTimeSpan = new TimeSpan(0,5,0),
                    BanReason = "Uber-spam - try again later"
                }
                );

            _BanThresholds.Sort(new Comparison<AttemptThreshold>((x, y) =>
                {
                    return x.AttemptTimeSpan.CompareTo(y.AttemptTimeSpan);
                }
            ));
        }

        private readonly object _SyncLock = new object();
        private readonly List<AttemptThreshold> _BanThresholds = new List<AttemptThreshold>();
        private readonly Dictionary<IPAddress, List<DateTime>> _AttemptLog = new Dictionary<IPAddress, List<DateTime>>(new IPAddressComparer());

        protected override void AttachToTelnetConnectionListener(INetworkConnectionListener connectionListener)
        {
            connectionListener.ConnectionAttempted += new ConnectionListenerEventHandler(ConnectionListener_ConnectionAttempted);    
        }

        void ConnectionListener_ConnectionAttempted(INetworkConnectionListener sender, INetworkConnection connection)
        {
            EnsureConnectionAtteptLogged(connection as TelnetSession);
        }

        private void EnsureConnectionAtteptLogged(TelnetSession connection)
        {
            lock (_AttemptLog)
            {
                if (!_AttemptLog.ContainsKey(connection.RemoteEndpoint.Address))
                {
                    _AttemptLog.Add(connection.RemoteEndpoint.Address, new List<DateTime>());
                    _AttemptLog[connection.RemoteEndpoint.Address].Add(connection.ConnectTime);
                }
                else
                {
                    var remoteAddressAttemptLog = _AttemptLog[connection.RemoteEndpoint.Address];
                    if(!remoteAddressAttemptLog.Contains(connection.ConnectTime))
                        remoteAddressAttemptLog.Add(connection.ConnectTime);
                }
            }
        }

        public override bool? ApproveConnectionAttempt(TelnetSession connection)
        {
            EnsureConnectionAtteptLogged(connection);
            var currentConnectTime = connection.ConnectTime;
            IEnumerable<AttemptThreshold> attemptThresholds;
            lock (_BanThresholds)
            {
                attemptThresholds = _BanThresholds.AsReadOnly();
            }
            IEnumerable<DateTime> attemptsLogged;
            lock (_AttemptLog)
            {
                attemptsLogged = _AttemptLog[connection.RemoteEndpoint.Address].AsReadOnly();
            }
            foreach (var threshold in attemptThresholds)
            {
                var violationTime =
                    (
                    from dt1 in attemptsLogged
                    where attemptsLogged.Where((dt2) => (dt2 - dt1) < threshold.AttemptTimeSpan).Count() > threshold.Count
                    select new Nullable<DateTime>(dt1)
                    ).OrderByDescending((dt) => dt).FirstOrDefault();
                if (violationTime.HasValue && ((currentConnectTime - violationTime.Value)<=threshold.BanTimeSpan))
                {
                    return false;
                }
            }
            return null;
        }

        public struct AttemptThreshold
        {
            public TimeSpan AttemptTimeSpan;
            public int Count;
            public TimeSpan BanTimeSpan;
            public string BanReason;
        }

    }


}
